JavaScript Promise 异步编程
为什么需要异步编程?
因为 JavaScript 大部分时候都是单线程,如果这时发生 IO 密集型和 CPU 密集型的任务需要处理时就会发生阻塞,前面讲过的 Web Workers 虽然也可以执行多任务的操作,但是它有个致命的缺点就是无法操作 DOM 元素,其次,虽然在 worker 里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。
异步 仅仅是推迟了某些代码的执行,对于一段需要长时间来执行的代码来说,异步只是将其的执行顺序移到后面,然后不阻塞其他的逻辑代码,等到其他代码执行完成,然后再执行这段代码,但是这段代码在执行的时候仍然还是会阻塞UI和其他业务代码 ,所以 CPU 密集型的操作还是需要靠 Web Workers
异步编程更适合一些 I/O操作 使CPU充分利用起来
在 JavaScript 代码中,有两种异步编程风格:老派callbacks,新派promise
CallBacks 的方式
参考资料 MDN--合作异步JavaScript
异步callbacks 其实就是回调函数,作为参数传递给那些在后台执行的其他函数. 当那些后台运行的代码结束,就调用callbacks函数
例如 JavaScript原生的事件系统传入的就是一个回调函数
// 第一个参数是侦听的事件类型,第二个就是事件发生时调用的回调函数。
btn.addEventListener('click', () => {
alert('You clicked me!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
Web提供给Javascript的一些异步代码
setTimeout()
:在指定的时间后执行一段代码.
let myGreeting = setTimeout(function() {
alert('Hello, Mr. Universe!');
}, 2000)
setInterval()
:以固定的时间间隔,重复运行一段代码.
function displayTime() {
let date = new Date();
let time = date.toLocaleTimeString();
document.getElementById('demo').textContent = time;
}
const createClock = setInterval(displayTime, 1000);
Promises 的方式
参考资料 MDN--优雅的异步处理
Promises 的三个状态
创建 Promises 时,它既不是成功也不是失败状态。这个状态叫作 pending(待定) 当 Promises 返回时,称为 resolved(已解决)。
- 一个成功的 resolved 称为 fullfilled(实现)。可以将
.then()
块链接到 Promises 链的末尾来链式调用 - 一个不成功的 resolved 被称为 rejected(拒绝)了。它返回一个错误原因(reason),同样可以把
.catch()
写到最后面用来捕获异常
状态改变只有两种可能
- 从 pending 变成 fullfilled
- 从 pending 变成 rejected
Promise 传递只要没有改变状态,状态是不会变的
function f1() {
return Promise.resolve(100)
}
f1().then(resolve => {
console.log(resolve);
}).then(resolve => {
console.log('还能打印吗?');
console.log(resolve); // 注意看这个是否输出了
})
/*
输出为:
100
还能打印吗?
*/
只要不去更改 then 的状态为 rejected,then 会一直传递下去(但是传的值不会传递下去)
Promise 主要的三个 API
p.then()
得到异步任务的正确结果p.catch()
获取异常信息p.finally()
成功与否都会执行
如何使用?
let promise = new Promise(传入一个函数);
// 注意:promise构造函数是同步执行的,then方法是异步执行的
let promise = new Promise((resolve,reject)=>{
if( 异步请求操作成功 ){
// 这个resolve用来传递参数
// 只要执行了这个 resolve 状态就变成了完成,下面的reject同理
resolve(value);
} else{
reject(error);
}
})
// 要等上面创建时传入的回调函数执行完了才会执行下面的then
promise.then(res =>{
console.log(res) //成功
}).catch(err =>{
console.log(err) //失败
})
then 的返回值
1、返回 Promise 实例对象
.then((res)=>{
return new Promise((resolve,reject)=>{
...
})
})
返回的该实例对象会调用下一个 then
2、返回普通值
.then((res)=>{return "hello promise"})
返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收值(实际上 then 函数会自动封装一个 Promise.resolve
对象出去,并把普通值传入 resolve 里)
Promise 包装多个任务
Promise.all([
$.ajax({ url: 'data/1.txt', dataType: 'json' }),
$.ajax({ url: 'data/2.txt', dataType: 'json' }),
$.ajax({ url: 'data/3.txt', dataType: 'json' })
]).then(arr => {
let [data1, data2, data3] = arr; //结构赋值
console.log(data1, data2, data3);
}, () => {
alert('失败了');
});
Promise.all
可以将多个 Promise 实例包装成一个新的 Promise 实例。同时返回值也是不同的,成功时返回的是一个结果数组,而失败时返回最先被 reject 失败状态的值
let p1 = new Promise((resolve, reject) => {
reject('成功了')
});
let p2 = new Promise((resolve, reject) => {
reject('success')
});
let p3 = Promise.reject('失败')
Promise.all([p1, p2]).then(resolve => {
console.log(resolve)
}).catch(err => {
console.log(err);
})
// 返回值为 ['成功','success'] 注意是数组!
Promise.all([p1, p2, p3]).then(resolve => {
console.log(resolve)
}).catch(err => {
console.log(err);
})
// 返回值为 '失败'
async/await
也是用来处理异步的,是 Generator 函数的改进,原理就是 Promise
一般这个 await
关键字就是用来处理 多个异步请求,下一个请求需要依赖上一个请求的结果
所以:await
关键字的作用就是把异步的请求转成同步,然后这个 async
把里面的操作整合成一个大的异步请求
注意看下面的例子
async function test() {
let a = await new Promise((res,rej)=>{
setTimeout(()=>{
console.log("执行完了 A");
// 必须把 res 状态传递出去,否则会一直阻塞在这里
res('A is OK!')
},2000);
})
let b = await new Promise((res,rej)=>{
setTimeout(()=>{
console.log("执行完了 B");
res('B is OK!')
},1000);
})
return b;
}
test(); // 这个函数是异步的
console.log("这里是 C");
// 执行结果 :
// 这里是 C
// 执行完了 A
// 执行完了 B
// 看上面的结果可以知道,await 实际就是把异步请求转成同步了(A 的等待时间是两秒,B 的才一秒)
也可以直接搭配 axios 使用
async function queryData(id) {
const info = await axios.get('/async1');
// 下一个请求要用到上一个请求的结果
const ret = await axios.get(`/async2?info=${info.data}`);
}
async/await 捕获异常
如果返回的是一个 reject
则需要捕获,由于 try-catch
只能用于同步代码里,所以必须加上 await
,还有一点就是 await
只能用于 async
函数
async function f1(){
return Promise.reject('这是一个错误')
}
// 如果直接调用会报错
f1()
//需要使用如下的方式捕获这个异常
// try catch 只能用于同步代码里,所以必须给 f1() 加上 await
// await只能用于 async 函数 所以..
async function f2(){
try {
await f1()
} catch (e) {
console.log(e)
}
}
// 调用 f2() 来捕获 f1() 的异常
f2()